package com.zjq.authoritysystem.filter;

import com.zjq.authoritysystem.common.properties.IgnoredUrlsProperties;
import com.zjq.authoritysystem.config.redis.Cache;
import com.zjq.authoritysystem.config.security.handler.LoginFailureHandler;
import com.zjq.authoritysystem.config.security.sevice.CustomerUserDetailsService;
import com.zjq.authoritysystem.exception.CustomerAuthenticationException;
import com.zjq.authoritysystem.utils.JwtUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

@Data
@Slf4j
@Component
public class CheckTokenFilter extends OncePerRequestFilter {

    @Resource
    private Cache cache;

    @Resource
    private JwtUtil jwtUtil;

    @Resource
    private CustomerUserDetailsService customerUserDetailsService;

    @Resource
    private LoginFailureHandler loginFailureHandler;


    /**
     * 登录地址
     */
    @Value("${request.login.url}")
    private String url;

    @Resource
    private IgnoredUrlsProperties ignoredUrlsProperties;

    /**
     * 过滤请求，进行token鉴权
     * @param request 请求
     * @param response  响应
     * @param filterChain 过滤器链
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            //进行token验证
            this.validateToken(request);
        } catch (AuthenticationException e) {
            loginFailureHandler.onAuthenticationFailure(request, response, e);
        }
        // 登录请求放行
        doFilter(request, response, filterChain);
    }

    /**
     * 判断白名单，跳过token鉴权
     * @param request 请求
     * @return 判断结果
     */
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        AntPathMatcher pathMatcher = new AntPathMatcher();
        String requestURI = request.getRequestURI();
        for (String url : ignoredUrlsProperties.getUrls()) {
            if (pathMatcher.match(url,requestURI)){
                return true;
            }
        }
        return false;
    }

    /**
     * 验证token
     *
     * @param request 请求信息
     */
    private void validateToken(HttpServletRequest request) {
        String token = request.getHeader("token");
        if (StringUtils.isBlank(token)) {
            token = request.getParameter("token");
        }
        if (StringUtils.isBlank(token)) {
            throw new CustomerAuthenticationException("token不存在");
        }
        //判断redis中是否存在token信息
        String tokenKey = "token_" + token;
        Object redisToken = cache.get(tokenKey);
        if (redisToken == null) {
            throw new CustomerAuthenticationException("登录身份信息失效,请重新登录");
        }
        if (!token.equals(redisToken.toString())) {
            throw new CustomerAuthenticationException("token验证失败");
        }

        //验证用户名
        String username = jwtUtil.getUsernameFromToken(token);
        if (StringUtils.isEmpty(username)) {
            throw new CustomerAuthenticationException("token解析失败");
        }
        //获取用户信息
        UserDetails userDetails = customerUserDetailsService.loadUserByUsername(username);
        if (userDetails == null) {
            throw new CustomerAuthenticationException("token验证失败");
        }

        /**
         *  Spring Security 提供的一种身份认证凭证，用于表示通过用户名和密码进行身份认证的用户凭证。它继承自 AbstractAuthenticationToken。
         *
         * UsernamePasswordAuthenticationToken 的构造函数有以下三个参数：
         *      principal：表示当前身份认证的主体，通常是用户对象或用户名。
         *      credentials：表示用户的凭证，如密码，通常为 null（因为敏感信息不应该在内存中明文存储）。
         *      authorities：表示用户的权限列表，通常是角色或权限对象的集合。
         */
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
                = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

        /**
         * 将 WebAuthenticationDetails 对象作为附加详细信息添加到 UsernamePasswordAuthenticationToken 中
         * WebAuthenticationDetails 是 Spring Security 提供的一个类，用于表示与 Web 请求相关的认证详细信息，如远程主机地址和会话ID等
         */
        usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

        /**
         * 将创建的 UsernamePasswordAuthenticationToken 对象设置为当前请求的身份验证信息。
         * SecurityContextHolder 是 Spring Security 提供的一个类，用于管理与当前线程关联的安全上下文信息。
         * 其中，SecurityContextHolder.getContext() 方法用于获取当前线程的安全上下文对象。
         * 通过调用 setAuthentication() 方法，并将创建的 UsernamePasswordAuthenticationToken 对象作为参数，
         * 可以将该身份验证信息设置到当前线程的安全上下文中。这意味着在后续的处理流程中，
         * 可以通过 SecurityContextHolder.getContext().getAuthentication() 方法获取到这个身份验证信息。
         */
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
    }

}
